Skip to content

Add s.svg() schema with themeable color variables - #480

Open
freekh wants to merge 10 commits into
mainfrom
claude/svg-schema-colors-tmi9jk
Open

Add s.svg() schema with themeable color variables#480
freekh wants to merge 10 commits into
mainfrom
claude/svg-schema-colors-tmi9jk

Conversation

@freekh

@freekh freekh commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Adds a new schema that stores an svg as a json node tree, so custom icons can be content rather than an opaque binary. Until now an svg could only be an s.image() / s.file() reference to a blob: not recolorable, not validatable against the design system, not diffable.

The point of the feature is that colors are declared as variables rather than baked hexes, so one icon supports currentColor, dark mode, and per-usage overrides.

Icons rendered in examples/next

From examples/next: the bookmark on the schema's example colors, the bell inside red text (its line variable maps to currentColor, so the clapper follows), and the check mapped to css custom properties this app owns.


Short form

<ValSvg src={icons.bell} size={32} />

Longer form

// content/icons.val.ts
export const iconSchema = s.svg({
  width: 24,
  height: 24,
  aspectRatio: "1:1",
  variables: {
    brand: { value: "#0055ff", match: ["#0055FF", "#0050f0"] },
    line: { value: "currentColor" },
    surface: { value: "#ffffff", match: ["#fff", "#fefefe"] },
  },
  // "forbid" (default) | "allow" | string[]
  literals: "forbid",
});

export const schema = s.record(iconSchema);

export default c.define("/content/icons.val.ts", schema, {
  bell: {
    viewBox: "0 0 24 24",
    width: 24,
    height: 24,
    children: [
      {
        tag: "path",
        attrs: { d: "M12 2.5A5.5 5.5 0 0 0 …", fill: { var: "brand" } },
        children: [],
      },
      {
        tag: "path",
        attrs: {
          d: "M9.6 18.5a2.4 2.4 0 0 0 4.8 0",
          stroke: { var: "line" },
          "stroke-width": 1.6,
          fill: "none",
        },
        children: [],
      },
    ],
  },
});
<ValSvg
  src={icons.bell}
  size={32}
  title="Notifications"
  vars={{
    brand: "var(--brand-500)",
    line: "currentColor",
    surface: null, // resolves from --val-svg-surface in css
  }}
/>

vars is exhaustive, like ValRichText's theme

vars maps each declared variable to the color that actually renders. It is optional, but if given it must cover every variable — so adding one to the schema is a compile error at every call site until someone revisits it. Same contract, and the same reason, as theme:

app/page.tsx(65,13): error TS2741: Property 'surface' is missing in type
  '{ brand: string; line: string; }' but required in type 'SvgVars<…>'.

null means "leave it to css" — that attribute becomes var(--val-svg-<name>, currentColor). Omitting vars does the same for every variable, and svgVarsCss(iconSchema) writes the schema's example colors into those properties, so a [data-theme="dark"] block retimes every icon with no React involved.

The value on a variable is an example: it drives the editor preview and the import match. Nothing is mirrored into the source — the palette lives only in the schema — so there is nothing that can drift and no new ValidationFix.

Raw colors are type-checked as well as validated. With the default literals: "forbid", fill: "#ff0000" is a TypeScript error, and the validator reports it with a source location:

 ✘  content/icons.val.ts:50:17
    Raw color '#ff0000' is not allowed. Use one of the declared variables: brand, line, surface

    50 |           fill: "#ff0000",
       |                 ^^^^^^^^^

Editor

Icons are read far more often than they are replaced, so the field is read-optimized: the tile is the control, and it is the only thing on screen. Drop an svg on it, paste markup into it, or click it to pick a file. No textarea, no button row; the markup sits behind an accordion.

Light Dark

Controls appear only when an import needs a decision. Colors are matched onto the palette by exact value or by a variable's match aliases; tolerance opts a variable into fuzzy matching. Nothing is snapped to a nearby variable otherwise — a brand color that is quietly rewritten is worse than one you are asked about.

While mapping, the tile previews the icon in its original colors, so you can see what you dropped and watch it move onto the palette as you pick. It is not committed until every color has somewhere to go — a half-mapped icon would silently lose fills.

Light Dark

Empty state, and the mapper on its own:

Empty Mapper

One source, three palettes, five sizes:

Light Dark

Stories: packages/ui/spa/components/fields/SvgField.stories.tsx (Fields/SvgField).

Docs

  • packages/next/README.md — a Svg section following the RichText section's shape: schema, initializing content, rendering, the vars note, editing, the type, and a full-custom renderer.
  • .agent/rules.md (shared by CLAUDE.md, the cursor rules and the copilot instructions, which all symlink to it) — a "Working with Svg" section covering the invariants that are easy to break, plus s.svg() in the type-hierarchy tables.

Decisions worth reviewing

Svg is excluded from stega encoding entirely. Every string in an svg (d, viewBox, points, transform) is machine parsed, so injecting invisible characters would corrupt the icon. That leaves attrs() with nothing to find, so the source path is attached as an ordinary serializable field (SVG_VAL_PATH) which ValSvg turns into data-val-path. A symbol would be cleaner json but would not survive RSC serialization. Tests assert every string round-trips byte-identical.

ValSvg builds React elements tag by tag — no dangerouslySetInnerHTML anywhere. Safety is therefore entirely the allowlist, and it is a strict per-tag allowlist of exact attribute names, not an on* denylist: React renders unknown attributes on host elements verbatim, and onload does fire on svg elements. Rejected: script, style (svg <style> is not scoped — it leaks to the whole document), foreignObject, a, use, image, all animation and filter elements; and the style / id / class / href / xlink:* / data-* attributes. After the enum-typed attributes, d, points, transform and stroke-dasharray are the only free-form strings left, and each is regex constrained and length capped.

The parser is hand rolled and dependency free (packages/shared/src/internal/svg/). @valbuild/shared ships into every user's server bundle, and svg-as-xml is a small grammar. Entity declarations and doctypes with an internal subset are rejected outright (XXE / billion laughs). Parser output is filtered through the allowlist before anything else sees it; the parser is not treated as a security boundary. DOMParser is avoided so the same code runs in jest, the CLI and node:vm.

Gradients are out of scope for now. Adding them later needs a third attribute value kind for url(#…) and per-instance id namespacing via useId() — without that, two icons on one page that both contain id="a" silently corrupt each other. Additive, not breaking.

@types/react-dom added to packages/react for the render tests. It resolves to the 18.2.17 already used by next / ui / examples, so there is still exactly one copy of @types/react and the duplicate-copy JSX hazard is not reintroduced.

.github/pr-assets/ is a separate commit and can be dropped before merge; it exists only so GitHub can render the screenshots above.

Verification

  • pnpm run lint
  • pnpm -w run format
  • pnpm run -r typecheck ✅ — this is what proves the ~18 never-checked dispatch sites are covered
  • pnpm test ✅ — 1165 tests, 99 suites
  • pnpm run build
  • cd examples/next && pnpm run build
  • pnpm exec tsx src/cli.ts validate --root ../../examples/next and node bin.js validate …content/icons.val.ts valid. This is the only path that exercises createServiceloadValModules, i.e. evaluating *.val.ts in the node:vm sandbox. The 2 remaining errors in the example app are the pre-existing missing-image / stale-metadata ones.

New tests: packages/core/src/schema/svg.test.ts; 20 rows in validation.test.ts (including onload rejected rather than silently dropped); cases in describe / hidden / readonly; 36 in packages/shared/src/internal/svg/parseSvg.test.ts (round-trip, color matching, and a rejection table for XXE, mismatched tags, <script>, onload=, xlink:href, oversized d); 13 in packages/react/src/internal/ValSvg.test.tsx (variable resolution, css fallback, size precedence, title/aria, data-val-path, plus a type-level check that vars stays exhaustive); and 5 in stegaEncode.test.ts.

One bug the round-trip test caught during development: parseSvg could not read back the var(--val-svg-*) form that svgToString emits, so "copy as svg" then re-paste in the editor would have dropped every variable. Fixed and covered.

claude added 2 commits August 20, 2026 18:47
Adds a new schema that stores an svg as a json node tree, so custom icons
can be content rather than an opaque binary. Until now an svg could only be
an s.image() / s.file() reference to a blob: not recolorable, not validatable
against the design system, not diffable.

Colors are declared as *variables* rather than baked hexes:

    s.svg({
      width: 24,
      height: 24,
      variables: { brand: "#0055ff", line: "currentColor" },
    })

The color on a variable is an example. It is what the editor previews, what
svgVarsCss() writes into the stylesheet, and what a pasted literal color is
matched against on import. What actually renders resolves from
--val-svg-<name>, so one icon supports currentColor, dark mode and per-usage
overrides:

    <style>{svgVarsCss(iconSchema)}</style>
    <ValSvg src={icons.bell} size={32} />
    <ValSvg src={icons.bell} size={32} vars={{ brand: "var(--danger)" }} />

    [data-theme="dark"] { --val-svg-brand: #6699ff }

How permissive to be about raw colors is up to the schema: `literals` is
"forbid" (the default), "allow", or an allowlist. This is enforced at the type
level as well as by the validator, so a raw hex in a .val.ts is a compile
error, not only a validation error.

Notable decisions:

- Svg sources are excluded from stega encoding entirely. Every string in an
  svg (d, viewBox, points, transform) is machine parsed, so injecting
  invisible characters would corrupt the icon. The source path is attached as
  an ordinary serializable field (SVG_VAL_PATH) instead, which ValSvg turns
  into data-val-path - a symbol would not survive RSC serialization.

- ValSvg builds React elements tag by tag; there is no innerHTML anywhere.
  Safety is therefore entirely the allowlist, and it is a strict per-tag
  allowlist of exact attribute names rather than an on* denylist: React
  renders unknown attributes on host elements verbatim, and onload does fire
  on svg elements. script, style, foreignObject, a, use, image, animation and
  filter elements are rejected, as are style/id/class/href/xlink/data-*.
  d, points, transform and stroke-dasharray are the only free-form strings
  left, and each is regex constrained and length capped.

- The svg parser is hand rolled and dependency free. @valbuild/shared ships
  into every user's server bundle, and svg-as-xml is a small grammar. Entity
  declarations and doctypes with an internal subset are rejected outright
  (XXE / billion laughs). Parser output is filtered through the allowlist
  before anything else sees it - the parser is not a security boundary.

- Import matches literal colors onto variables by exact normalized value, or
  by a variable's declared match aliases. Nothing is snapped to a nearby
  variable unless that variable opted in with `tolerance`; anything left over
  is reported so the editor can ask. Quietly rewriting a brand color is worse
  than asking about it.

- No new ValidationFix code: because the palette lives only in the schema and
  is never mirrored into the source, there is nothing that can drift and
  nothing to repair.

Gradients are deliberately out of scope for now. Adding them later needs a
third attribute value kind for url(#...) plus per-instance id namespacing, and
is additive rather than breaking.

Also adds the editor field (paste markup or drop a .svg, with a prompt for
colors that are not in the palette), storybook stories for it, and an icons
module in examples/next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3Wbr5HZDGfdRzAnZ5aH7n
Storybook and example-app screenshots referenced from the pull request body.
Isolated in its own commit so it can be dropped before merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3Wbr5HZDGfdRzAnZ5aH7n
@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3e313bb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@valbuild/core Patch
@valbuild/shared Patch
@valbuild/react Patch
@valbuild/next Patch
@valbuild/server Patch
@valbuild/ui Patch
@valbuild/language-server Patch
@valbuild/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Follow-up on review.

ValSvg's `vars` now works exactly like ValRichText's `theme`: optional, but
exhaustive if given. Every variable the schema declares must be mapped to a
color, so adding one to `s.svg({ variables })` is a compile error at every call
site until someone revisits it - which is the point. A value is any css color,
`null` means "leave it to css" and emits var(--val-svg-<name>, currentColor).

Reworked the editor field around how it is actually used. Icons are read far
more often than they are replaced, so the tile *is* the control and it is the
only thing on screen by default: drop an svg on it, paste markup into it, or
click it to pick a file. The textarea and the button row are gone, the markup
is behind an accordion, and the mapping controls appear only when an import
needs a decision. While mapping, the tile previews the icon being imported in
its original colors, so you can see what you dropped and watch it move onto the
palette as you pick - it is not committed until every color has somewhere to go.

Docs: a Svg section in packages/next/README.md following the RichText section's
shape (schema, initializing, rendering, the vars note, editing, the type, full
custom), and a "Working with Svg" section in .agent/rules.md - shared by
CLAUDE.md, the cursor rules and the copilot instructions, which all symlink to
it - covering the invariants that are easy to break: never stega encode an svg,
the allowlist is the whole security boundary, the parser is not, vars is
exhaustive on purpose, and the palette lives only in the schema.

Also adds ValSvg render tests (variable resolution, the css fallback, size
precedence, title/aria, data-val-path) plus a type-level check that vars stays
exhaustive. That needed @types/react-dom in packages/react; it resolves to the
18.2.17 already used by next/ui/examples, so there is still exactly one copy of
@types/react and the duplicate-copy JSX hazard is not reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3Wbr5HZDGfdRzAnZ5aH7n
@freekh
freekh requested a lite review from Copilot August 20, 2026 19:38
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3Wbr5HZDGfdRzAnZ5aH7n

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces first-class s.svg() support across Val’s core/schema system, shared SVG parsing/serialization utilities, React/Next rendering (ValSvg), and the editor UI field (SvgField), enabling themeable, schema-validated SVG icons stored as a JSON node tree instead of opaque files.

Changes:

  • Add SvgSource/SvgSchema to core type unions, schema serialization/deserialization, validation, and selector mapping.
  • Add shared SVG XML parsing + color normalization + svgToString round-tripping utilities with tests.
  • Add UI editor support (field, previews, search/index behaviors) and React/Next rendering APIs + docs + example usage.

Reviewed changes

Copilot reviewed 57 out of 69 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks new dev dependency resolution (@types/react-dom) and updated platform metadata entries.
packages/ui/spa/ValSyncEngine.ts Treats svg as non-interdependent for sync behavior.
packages/ui/spa/utils/traverseSchemaSource.ts Adds svg leaf handling for schema/source traversal.
packages/ui/spa/utils/schemaTypesOfPath.ts Allows traversal into svg internal structure for path typing.
packages/ui/spa/utils/getDependentModuleFiles.ts Excludes svg from dependent module file enumeration.
packages/ui/spa/utils/findRequiredRemoteFiles.ts Declares svg as never requiring remote files.
packages/ui/spa/search/createSearchIndex.ts Indexes svg fields by path only (no tokenizing icon internals).
packages/ui/spa/resolvePatchPath.ts Treats svg similarly to richtext for patch path resolution.
packages/ui/spa/components/ValProvider.tsx Adds svg to shallow source typing + mapping logic.
packages/ui/spa/components/ValFieldProvider.tsx Adds svg to shallow source typing + mapping logic.
packages/ui/spa/components/Preview.tsx Adds svg preview rendering entrypoint.
packages/ui/spa/components/NodeIcon.tsx Adds an icon for svg nodes in the schema tree UI.
packages/ui/spa/components/getReferencedFiles.ts Excludes svg from referenced-file scanning.
packages/ui/spa/components/getKeysOf.ts Excludes svg from key discovery logic.
packages/ui/spa/components/fields/SvgField.tsx New SVG editor field (drop/paste/import mapping) + preview renderer.
packages/ui/spa/components/fields/SvgField.stories.tsx Storybook stories for svg field states and rendering.
packages/ui/spa/components/fields/emptyOf.ts Defines default empty svg JSON shape.
packages/ui/spa/components/AnyField.tsx Wires SvgField into the generic field renderer.
packages/shared/src/internal/zod/SerializedSchema.ts Extends shared Zod schema validation to include serialized svg schema/options.
packages/shared/src/internal/svg/xml.ts Adds minimal XML reader/encoder helpers used by svg parsing.
packages/shared/src/internal/svg/svgToString.ts Adds svg serialization + JSON patch-friendly conversion utilities.
packages/shared/src/internal/svg/parseSvg.ts Adds svg markup parser with allowlist filtering and variable/literal color mapping.
packages/shared/src/internal/svg/parseSvg.test.ts Tests parsing, allowlist behavior, rejection cases, and round-tripping.
packages/shared/src/internal/svg/index.ts Exposes shared svg internal API surface.
packages/shared/src/internal/svg/colors.ts Adds color parsing/normalization and tolerance matching utilities.
packages/shared/src/internal/index.ts Re-exports svg internal utilities from shared internal barrel.
packages/server/src/hasRemoteFileSchema.ts Marks svg as non-remote-file schema for server logic.
packages/react/src/stega/stegaEncode.ts Exempts svg from stega encoding and attaches _valPath instead.
packages/react/src/stega/stegaEncode.test.ts Adds tests ensuring svg strings remain byte-identical and path tagging works.
packages/react/src/stega/index.ts Exposes svg stega type surface.
packages/react/src/internal/ValSvg.tsx Adds ValSvg renderer with variable resolution and accessibility behavior.
packages/react/src/internal/ValSvg.test.tsx Tests variable resolution, sizing precedence, aria/title behavior, and data-val-path.
packages/react/src/internal/index.ts Exports ValSvg + types from react internal entrypoint.
packages/react/package.json Adds @types/react-dom dev dependency for new render-to-string tests.
packages/next/src/external_exempt_from_val_quickjs.ts Exposes svg core + react exports through Next’s QuickJS exemption surface.
packages/next/README.md Documents s.svg() usage, rendering (ValSvg), vars contract, and editor behavior.
packages/core/src/source/svg.ts Introduces the Svg source/types model and _valPath constant.
packages/core/src/source/index.ts Adds svg to the core Source union.
packages/core/src/selector/svg.ts Adds SvgSelector type for source-to-selector parity.
packages/core/src/selector/index.ts Adds svg to selector conditional mapping and SelectorSource union.
packages/core/src/schema/validation.test.ts Adds svg validation cases into generic schema validation suite.
packages/core/src/schema/svg/allowlist.ts Defines svg tag/attr allowlist and constraints used as security boundary.
packages/core/src/schema/svg.ts Implements SvgSchema, validation rules, and svgVarsCss helper.
packages/core/src/schema/svg.test.ts Tests schema serialization, validation, builder methods, and svgVarsCss.
packages/core/src/schema/readonly.test.ts Ensures svg schema respects .readonly() serialization.
packages/core/src/schema/index.ts Adds svg schema to the serialized schema union / assert typing.
packages/core/src/schema/hidden.test.ts Ensures svg schema respects .hidden() serialization.
packages/core/src/schema/deserialize.ts Adds svg support to schema deserialization.
packages/core/src/schema/describe.test.ts Ensures svg schema .describe() survives serialize/deserialize round-trip.
packages/core/src/module.ts Allows path resolution to traverse into svg internals while keeping schema pinned.
packages/core/src/initSchema.ts Exposes s.svg() in the schema initializer surface and docs.
packages/core/src/index.ts Exports svg types, schema, and allowlist helpers from core public API.
examples/next/val.modules.ts Registers the new icons.val module in the example app.
examples/next/content/icons.val.ts Adds example icon schema/content demonstrating variables and usage.
examples/next/app/page.tsx Demonstrates ValSvg rendering variants in the example app.
.github/pr-assets/svg-schema/README.md Adds PR review asset notes for screenshots.
.agent/rules.md Documents svg invariants and adds svg to the type hierarchy docs.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/ui/spa/components/ValProvider.tsx
Comment thread packages/ui/spa/components/ValFieldProvider.tsx
Comment thread packages/shared/src/internal/zod/SerializedSchema.ts Outdated
Comment thread packages/shared/src/internal/svg/xml.ts Outdated
- `decodeXmlEntities` called `String.fromCodePoint` on any FINITE code point.
  `&#1114112;` and `&#x7FFFFFFF;` are finite and out of range, so parsing svg
  markup that contains one threw a RangeError instead of reporting the markup.
  Out-of-range entities are now left as written. The `#X` (uppercase) branch was
  dead — the regex only matches lowercase `x`, which is also the only spelling
  the XML CharRef production allows — so it is gone and its case is covered.

- `SerializedSvgSchema` used `SvgOptions.optional() as any`. The mismatch was
  one field: `aspectRatio` is `number | \`${number}:${number}\`` in core, and
  `z.string()` parses to `string`. Narrowing that field with a `z.custom` lets
  the options object be typed as `z.ZodType<SvgOptions>` and the `as any` go,
  so the other seven fields are checked against core again.

- The two svg field-provider messages reported an array and a `null` as
  "object", since `typeof` cannot tell them apart. They now report the runtime
  kind. The neighbouring pre-existing messages are left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFs5x1hQ9MiaDn4aQyANTp

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 71 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

packages/ui/spa/components/fields/SvgField.tsx:210

  • SvgColorMapper uses style={{ background: color.raw }} for unmatched colors. color.raw comes directly from imported SVG markup (and can be an arbitrary string such as url(...) when the color is unparseable), so this can unintentionally apply non-color CSS values / trigger external loads in the editor. Use backgroundColor and only set it for parsed/normalized colors.

Comment thread .changeset/svg-schema.md Outdated
claude added 5 commits August 22, 2026 15:59
…ors-tmi9jk

Six conflicts, all from #453 landing on main.

Source / Selector: both sides add a source type to the same unions -
main's JsonSource and this branch's SvgSource. Both are kept, and the
Selector<T> chain gains a rung for each.

search: main extracted the index building out of search.worker.ts into
searchIndex.ts and deleted createSearchIndex.ts. This branch's only
change there was to index an svg by its path (nothing inside an icon is
searchable text), which is carried over to searchIndex.ts. In the new
shape `index.add` already prefixes the cleaned path and skips an entry
with no searchText at all, so the svg case sets one - "svg", which
doubles as a way to list every icon, mirroring richtext's fallback label.

ValFieldProvider: adjacent imports, both kept.

Two follow-ons the textual merge could not see:

- Schema gained an abstract executeCustomValidateAt, so SvgSchema has to
  implement it (same shape as DateSchema's).
- jsonValuesLoadRequirements has a never-guarded switch over every schema
  type; an svg holds no reference to another module, so it joins the ones
  that return false. Without it the guard would fall to its conservative
  default and demand a full entry load for any record containing an icon.
…rthand

`color.raw` in SvgColorMapper is arbitrary text lifted out of imported svg
markup - anything that appeared as a `fill` or `stroke` and did not parse as a
color. `background` is a shorthand that also accepts `url(...)` and gradients,
so importing a hostile icon rendered an external request from inside the editor.

The swatch now paints `backgroundColor` from `normalized`, which is always a
plain `#rrggbb[aa]` out of our own parser; a color that did not parse gets an
empty swatch, with its text right beside it. The variable swatch in the dropdown
gets the same treatment - that value is the schema author's own, so it was never
the same exposure, but there is no reason for the shorthand there either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFs5x1hQ9MiaDn4aQyANTp
It described behaviour this version does not have: `id` is not an allowed
attribute at all, so there is nothing to prefix and no `url(#…)` reference to
rewrite. Gradients, masks and clip paths are out of scope for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFs5x1hQ9MiaDn4aQyANTp
…ors-tmi9jk

Five conflicts, all where #477's s.color() landed on main in the same
places this branch adds s.svg(). Every one is two independent additions
at one insertion point, so both sides are kept:

- jsonValuesLoadRequirements: "color" and "svg" both fall through to the
  no-referrer branch.
- stegaEncode: adjacent type imports.
- describe.test.ts: a serialize test and a round-trip test each.
- examples/next: the theme module and the icons module are both
  registered, and the home page renders both sections.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFs5x1hQ9MiaDn4aQyANTp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants